package com.ssr.master.common.shiro;

import com.ssr.master.common.utils.StringUtils;
import com.ssr.master.modules.entity.SysMenuDict;
import com.ssr.master.modules.entity.SysPower;
import com.ssr.master.modules.entity.SysRole;
import com.ssr.master.modules.entity.SysUser;
import com.ssr.master.modules.service.SysMenuDictService;
import com.ssr.master.modules.service.SysPowerService;
import com.ssr.master.modules.service.SysRoleService;
import com.ssr.master.modules.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author ZhangPeng
 * @Describe //todo
 * @date
 * @updateBy
 * @other
 **/

public class ShiroRealm extends AuthorizingRealm {

    private static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);

    //如果项目中用到了事物，@Autowired注解会使事物失效，可以自己用get方法获取值
    @Autowired
    private SysRoleService roleService;
    @Autowired
    private SysUserService userService;
    @Autowired
    private SysPowerService sysPowerService;
    @Autowired
    private SysMenuDictService sysMenuDictService;


    /**
     * 认证信息.(身份验证) : Authentication 是用来验证用户身份
     * 提供账户信息返回认证信息（用户的角色信息集合）
     * DisabledAccountException （禁用的帐号）
     * LockedAccountException （锁定的帐号）
     * UnknownAccountException（错误的帐号）
     * ExcessiveAttemptsException（登录失败次数过多）
     * IncorrectCredentialsException （错误的凭证）
     * ExpiredCredentialsException （过期的凭证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        String name = token.getUsername();
        String password = String.valueOf(token.getPassword());
        SysUser user = new SysUser();
        user.setLoginName(name);
        user.setPassword(password);
        // 从数据库获取对应用户名密码的用户
        SysUser sysUser = userService.getByLoginName(user);
        if (sysUser != null) {
            // 用户为禁用状态
            if ("1".equals(sysUser.getLoginFlag())) {
                throw new LockedAccountException("账号已被锁定,请联系管理员！");
            }
            logger.info("---------------- Shiro 凭证认证成功 ----------------------");
            // principal参数使用用户Id，方便动态刷新用户权限
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    sysUser,
                    sysUser.getPassword(),
                    getName()
            );
            return authenticationInfo;
        } else {
            if (sysUser == null) {
                throw new UnknownAccountException("账号不存在！");
            }
        }
        throw new UnknownAccountException();
    }

    /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     * <p>
     * shiro的权限授权是通过继承AuthorizingRealm抽象类，重载doGetAuthorizationInfo();
     * 当访问到页面的时候，链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话，那么这个方法可以不进行实现，直接返回null即可。
     * <p>
     * 在这个方法中主要是使用类：SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     * <p>
     * 当然也可以添加set集合：roles是从数据库查询的当前用户的角色，stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     * <p>
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     * <p>
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002]，perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     * <p>
     * 授权 权限认证，为当前登录的Subject授予角色和权限（角色的权限信息集合）
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("---------------- 执行 Shiro 权限获取 ---------------------");
        Object principal = principals.getPrimaryPrincipal();
        // 权限信息对象info,用来存放查出的用户的所有的角色（role）及权限（permission）
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        if (principal instanceof SysUser) {
            SysUser userLogin = (SysUser) principal;
            // 赋予角色
            List<SysRole> roleList = roleService.getUserRoleList(userLogin.getId());
            for (SysRole roles : roleList) {
                authorizationInfo.addRole(roles.getRoleName());
            }

            // 赋予权限
            List<SysMenuDict> sysPowerList = null;

            // 从数据库获取对应用户名密码的用户
            SysUser sysUser = userService.get(userLogin.getId());
            if (null == sysUser) {
                return authorizationInfo;
            }
            // ROOT用户默认拥有所有权限
            if ("0".equals(sysUser.getUserType())) {
                sysPowerList = sysMenuDictService.listAllPower("");
            } else {
                sysPowerList = sysMenuDictService.listAllPower(sysUser.getId());
            }

            if (!CollectionUtils.isEmpty(sysPowerList)) {
                Set<String> permissionSet = new HashSet<String>();
                for (SysMenuDict sysMenuDict : sysPowerList) {
                    if (StringUtils.isNotBlank(sysMenuDict.getPerms())) {
                        permissionSet.addAll(Arrays.asList(sysMenuDict.getPerms().trim().split(",")));
                    }
                }
                authorizationInfo.setStringPermissions(permissionSet);
            }
//            Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
//            authorizationInfo.addStringPermissions(permissions);
        }
        logger.info("---- 获取到以下权限 ----");
        logger.info(authorizationInfo.getStringPermissions().toString());
        logger.info("---------------- Shiro 权限获取成功 ----------------------");
        return authorizationInfo;
    }


    /**
     * @param principals
     * @return void
     * @Description //todo 重写方法,清除当前用户的的 授权缓存
     * @author ZhangPeng
     * @date 2020/6/29 17:02
     * @other
     **/
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * @param principals
     * @return void
     * @Description //todo 重写方法，清除当前用户的 认证缓存
     * @author ZhangPeng
     * @date 2020/6/29 17:03
     * @other
     **/
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }


    /**
     * @param
     * @return void
     * @Description //todo 自定义方法：清除所有 授权缓存
     * @author ZhangPeng
     * @date 2020/6/29 17:03
     * @other
     **/
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }


    /**
     * @param
     * @return void
     * @Description //todo 自定义方法：清除所有 认证缓存
     * @author ZhangPeng
     * @date 2020/6/29 17:04
     * @other
     **/
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }


    /**
     * @param
     * @return void
     * @Description //todo 自定义方法：清除所有的  认证缓存  和 授权缓存
     * @author ZhangPeng
     * @date 2020/6/29 17:04
     * @other
     **/
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }
}
